"use strict"
import init,{shogia_init} from "./pkg/shogia.js"
import { BOARD_SIZE, Board, Loc, Move, P_EMPTY, Piece_class, Piece_teban, STATUS_GOTE_WIN, STATUS_NORMAL, STATUS_SENTE_WIN, TEBAN_GOTE, TEBAN_SENTE, teban_from_str, teban_swap } from "./game.js"

const Out = {
    reply(...args){
        self.postMessage(args.join(' '))
    },
    reply_ok(...args){
        Out.reply("ok", ...args)
    },
    report_error(msg){
        self.postMessage(`[ERROR] ${msg}`)
    },
}

const State = {
    engine_usable: false,
    uct_times_max: 1024,
}

const ZOBRIST_P_LEN = 31
const ZOBRIST_ROW_LEN = BOARD_SIZE * ZOBRIST_P_LEN
const ZOBRIST_LEN = BOARD_SIZE * ZOBRIST_ROW_LEN
const ZOBRIST = new Uint32Array(ZOBRIST_LEN)
for (let i = 0; i < ZOBRIST_LEN; i++){
    ZOBRIST[i] = Math.floor(Math.random() * 4294967295)
}

function hash(board) {
    let code = 0
    for (let y = 0; y < BOARD_SIZE; y += 1) {
        for (let x = 0; x < BOARD_SIZE; x += 1) {
            const p = board.get(Loc(y,x))
            const p_teban = Piece_teban(p)
            const p_class = Piece_class(p)
            const i = ((y * ZOBRIST_ROW_LEN) + (x * ZOBRIST_P_LEN)) + ((p == P_EMPTY) ? 0 : ((2-p_teban) * 15) + p_class)
            code ^= ZOBRIST[i]
        }
    }
    return code
}

class HashBoard {
    constructor(_from) {
        if (R.is(HashBoard, _from)) {
            this._board = new Board(_from._board)
            this._hash = _from.hash
        } else {
            this._board = new Board(_from)
            this._hash = hash(this._board)
        }
    }
    reset(_from) {
        if (R.is(HashBoard, _from)) {
            this._board.reset(_from._board)
            this._hash = _from.hash
        } else {
            this._board = new Board(_from)
            this._hash = hash(this._board)
        }
    }
    hash() {
        return this._hash
    }
    move(m) {
        if (this._board.move(m)) {
            this._hash = hash(this._board)
        }
    }
    all_moves(teban) {
        return this._board.all_moves(teban)
    }
}

const VALUATION_MIN = Number.MIN_SAFE_INTEGER
const VALUATION_MAX = Number.MAX_SAFE_INTEGER
const VALUATION_P = [
    0,
    0,              // OU
    65536,          // HI
    32768,          // KAKU
    1024,           // KIN
    512,            // GIN
    32,             // KEI
    16,             // KYOU
    1,              // FU
    1048576,        // RYU
    524288,         // UMA
    1024,           // NARIGIN
    1024,           // NARIKEI
    1024,           // NARIKYO
    1024,           // TOKIN
]
const VALUATION_TEBAN = [
    0, -1, 1
]

function valuation(board) {
    let score = 0
    for (let y = 0; y < BOARD_SIZE; y += 1) {
        for (let x = 0; x < BOARD_SIZE; x += 1) {
            const p = board.get(Loc(y,x))
            score += VALUATION_P[Piece_class(p)] * VALUATION_TEBAN[Piece_teban(p)]
        }
    }
    for (const inhand of board.get_inhands(TEBAN_GOTE)) {
        score -= VALUATION_P[inhand[0]] * inhand[1]
    }
    for (const inhand of board.get_inhands(TEBAN_SENTE)) {
        score += VALUATION_P[inhand[0]] * inhand[1]
    }
    return score
}

class Node {
    constructor(teban, board, move=null) {
        this._teban = teban
        this._move = move
        this._nexts = []
        this._next_moves = board.all_moves()
        this._has_over = (board.status() != STATUS_NORMAL)
        this.n_score = 0
        this.n_select = 0
    }

    is_over() {return this._has_over}

    is_fully() {return (this._next_moves.length < 1)}

    get_move() {return this._move}

    expand(prev_board) {
        if (this.is_over()) {return null}
        if (this.is_fully()) {return null}

    }
}

function step_uct(teban, board, init_moves, max_times) {
    const root = new Node(teban, board);
    for (let times = 0; times < max_times; times += 1) {
        const node = uct_select(root)
        const add = uct_simulate(node)
        uct_backup(node, add)
    }
    return [uct_select(root)]
}

function hit_base_move(teban, board, moves, depth=3) {
    if (depth > 0) {
        const board_last = new HashBoard(board)
        for (const m of moves) {
            if (board.move(m)) {
                if (((teban == TEBAN_SENTE) && (board.status() == STATUS_SENTE_WIN)) ||
                    ((teban == TEBAN_GOTE) && (board.status() == STATUS_GOTE_WIN))) {
                    return m
                } else if (board.status() == STATUS_NORMAL) {
                    const board_last = new HashBoard(board)
                    for (const m of board.all_moves(teban_swap(teban))) {
                        if (board.move(m)) {
                            const hit =  hit_base_move(teban, board, board.all_moves(teban), depth-1)
                            if (hit != undefined) {return hit}
                        }
                        board.reset(board_last)
                    }
                }
            }
            board.reset(board_last)
        }
    }
    return undefined
}

function step(teban, board) {
    const init_moves = board.all_moves(teban)
    if (init_moves.length > 0) {
        const base_move = hit_base_move(teban, new HashBoard(board), init_moves)
        if (R.is(Move, base_move)) {
            return [base_move]
        } else {
            //return step_uct(teban, board, init_moves, State.uct_times_max)
        }
    }
    return init_moves
}

const Take = {}
Take["step"] = (teban_str, board_str)=>{
    const teban = teban_from_str(teban_str)
    const board = new HashBoard(board_str)
    let moves = []
    if (State.engine_usable) {
        moves = step(teban, board)
    }
    if (moves.length > 0) {
        const m = moves[Math.floor(Math.random() * moves.length)]
        Out.reply("step", m.to_str())
    } else {
        Out.reply("step", "pass")
    }
}
Take["valuation"] = (board_str)=>{
    const board = new Board(board_str)
    Out.reply_ok("valuation", valuation(board))
}
Take["reset"] = ()=>{
    console.log("(reset)")
}
Take["raii"] = ()=>{
    console.log("(raii)")
}

;(()=>{
    init().then(()=>{
        const msg = shogia_init()
        State.engine_usable = Boolean(msg)
        if (State.engine_usable) {
            console.log(msg)
        }
    })
    self.onmessage = function(e){
        const tokens = e.data.split(' ')
        if (tokens.length > 0){
            const head = tokens[0]
            if (Take.hasOwnProperty(head)) {
                Take[head](...(tokens.slice(1)))
            } else {
                Out.report_error(e.data)
            }
        }
    }
    Out.reply("ready")
})()
